<?php
declare(strict_types=1);

namespace LowCode;

use Closure;
use LowCode\Abstracts\Core;
use LowCode\Support\JsApi;
use LowCode\Support\JsDs;
use LowCode\Support\JsExp;
use LowCode\Support\JsFn;
use LowCode\Traits\HasCall;
use LowCode\Traits\HasDirective;
use LowCode\Traits\HasElementEvents;
use LowCode\Traits\HasProps;
use LowCode\Traits\HasSlotDefault;
use LowCode\UI\Content;

/**
 * @method static static make(string $name = null, array $props = []) 创建节点
 * @method $this id(string $id) 设置节点ID
 * @method $this from(string|array $value) 设置组件来源
 * @method $this locked(bool $bool = true | false) 是否锁定
 * @method $this invisible(bool $bool = true | false) 是否不可见的
 * @method $this props(array $props) 设置组件props
 * @method $this events(array $events) 设置组件事件
 * @method $this directives(array $directives) 设置组件指令
 * @property string|null $id
 * @property string|null $name
 * @property bool|null $locked
 * @property bool|null $invisible
 * @property array $props
 * @property mixed $children
 */
class Node extends Core
{
    protected ?Block $block = null;

    protected Store $store;

    protected array $state = [];

    protected array $attributes = [
        "name"=>"div"
    ];

    protected static array $allowAttributes = [
        "id","name","from","locked","invisible","props","events","directives"
    ];

    use HasProps, HasSlotDefault, HasDirective, HasCall;

    use HasElementEvents;

    public function __construct(string $name = null, array $props = [])
    {
        parent::__construct();
        $this->componentName($name?:class_basename($this));
        if ($props){
            $this->props($props);
        }
        $this->store = Store::make();
    }

    /**
     * 设置节点名称
     * @param string|JsExp $name
     * @return $this
     */
    public function componentName(string|JsExp $name): static
    {
        if ($name instanceof JsExp){
            return $this->setAttribute("name",$name);
        }else{
            return $this->setAttribute("name",convertToCamelCase($name));
        }
    }

    /**
     * 设置state状态数据
     * @param string $key
     * @param mixed|JsExp|JsFn|null $value
     * @return $this
     */
    public function state(string $key, mixed $value = null): static
    {
        $this->state[$key] = $value;
        return $this;
    }

    /**
     * 设置state状态数据
     * @param array $states
     * @return $this
     */
    public function states(array $states): static
    {
        $this->state = array_merge($this->state, $states);
        return $this;
    }

    /**
     * 获取state状态数据
     * @return array
     */
    public function getState(): array
    {
        return $this->state;
    }

    /**
     * 设置组件来源于依赖包
     * @param string $packageName
     * @return $this
     */
    public function fromPackage(string $packageName): static
    {
        return $this->from($packageName);
    }

    /**
     * 设置组件来源来源于其他Schema对象
     * @param string $id
     * @return $this
     */
    public function fromSchema(string $id): static
    {
        return $this->from(["type"=>"Schema","id"=>$id]);
    }

    /**
     * 来源于远程的Schema文件
     * @param string $url
     * @return $this
     */
    public function fromUrl(string $url): static
    {
        return $this->from(["type"=>"UrlSchema","url"=>$url]);
    }

    /**
     * 来源远程组件
     * @param string[] $urls
     * @param string|null $library 依赖
     * @return $this
     */
    public function fromPlugin(array $urls, string $library = null): static
    {
        return $this->from(["type"=>"Plugin","urls"=>$urls, "library"=>$library]);
    }

    /**
     * 设置组件事件
     * @param string $name
     * @param JsFn|string $handler
     * @param array<string, bool> $modifiers
     * @return $this
     */
    public function event(string $name, JsFn|string $handler, array $modifiers = []): static
    {
        if (is_string($handler)){
            $handler = JsFn::make($handler);
        }
        $events = $this->get("events",[]);
        $events[$name] = [
            "name"=>$name,
            "handler"=>$handler,
            "modifiers"=>$modifiers,
        ];
        return $this->events($events);
    }

    /**
     * 设置组件 slot
     * @param string $slotName
     * @param array $params
     * @return $this
     */
    public function slot(string $slotName, array $params = []): static
    {
        if ($params){
            $this->setAttribute("slot",[
                "name"=> $slotName,
                "params"=> $params
            ]);
        }else{
            $this->setAttribute("slot", $slotName);
        }
        return $this;
    }



    /**
     * @param string|JsExp|Node|Node[] $children
     * @return static
     */
    public function children(mixed $children): static
    {
        if ($children === null) return $this;
        if (is_resource($children)) return $this;
        $nodes = $this->get("children",[]);
        if (!is_array($nodes)||!array_is_list($nodes)){
            if (is_scalar($nodes)){
                $nodes = Content::make($nodes);
            }
            $nodes = [$nodes];
        }
        $this->repeatChildren($children, $nodes);
        if (count($nodes)==1&&!($nodes[0] instanceof Node)){
            $nodes = $nodes[0];
        }
        return $this->setAttribute("children", $nodes);
    }

    protected function repeatChildren(mixed $children, &$nodes): array
    {
        if ($children instanceof Closure){
//            $macro = $children->bindTo($this, static::class);
            $callResult = call_user_func($children, $this);
            if ($callResult && !is_callable($callResult)){
                $this->repeatChildren($callResult, $nodes);
            }
        }elseif (is_array($children)&&array_is_list($children)){
            foreach ($children as $child){
                $this->repeatChildren($child, $nodes);
            }
        }else{
            if (is_numeric($children)){
                $children = strval($children);
            }elseif (is_bool($children)){
                $children = $children?"true":"false";
            }elseif ($children instanceof Node){
                $this->states($children->getState());
            }else{
                if (is_string($children)){
                    if (str_starts_with($children,"\${")&&str_ends_with($children,"}")){
                        $children = jsExp(str_replace(["\${","}"],"",$children));
                    }
                }
            }
            $nodes[] = $children;
        }
        return $nodes;
    }


    /**
     * @param string $key
     * @param string $value
     * @param string|null $id
     * @return $this
     */
    public function jsExpression(string $key, string $value, string $id = null): static
    {
        return $this->prop($key, JsExp::make($value, $id));
    }

    /**
     * @param string $key
     * @param string $value
     * @param string|null $id
     * @return $this
     */
    public function jsFunction(string $key, string $value, string $id = null): static
    {
        return $this->prop($key, JsFn::make($value, $id));
    }

    /**
     * @param JsApi $jsApi
     * @return $this
     */
    public function addApi(JsApi $jsApi): static
    {
        $this->store->addApi($jsApi);
        return $this;
    }

    /**
     * @param string $blockId
     * @param Block $block
     * @return $this
     */
    public function addBlock(string $blockId, Block $block): static
    {
        $this->store->id($blockId)->addBlock($block);
        return $this;
    }

    /**
     * @param string $name
     * @param JsDs $dataSource
     * @return $this
     */
    public function dataSource(string $name, JsDs $dataSource): static
    {
        $this->store->dataSource($name, $dataSource);
        return $this;
    }

    public function addDependencies(Dependencies $dependencies): static
    {
        $this->store->addDependencies($dependencies);
        return $this;
    }

    /**
     * @return array
     */
    public function toArray(): array
    {
        $this->block?->states($this->state);
        return parent::toArray();
    }

    public function __call($method, $parameters)
    {
        return $this->toCall($method, $parameters);
    }


}
